﻿using System;
using System.Collections.Generic;

using OpenTK;

namespace MosCraft.Utilities
{
    public class RayTracer
    {
        public struct Hit
        {
            public int x, y, z;
            public BlockFace face;
        }

        // This method is a *lot* faster than using (int)Math.floor(x)
        private static int fastfloor(double x)
        {
            return x > 0 ? (int)x : (int)x - 1;
        }

        /* traces a ray starting from a point to a direction
         returns in order every block that intersects the ray
         the return value is a x,y,z coordinate of the intersecting
         voxel as well as a w which describes which face was intersected
             The algorithm is inspired by 
         "A Fast Voxel Traversal Algorithm for Ray Tracing"
         by John Amanatides and Andrew Woo */

        public static IEnumerable<Hit> trace(Vector3d Postion, Vector3d Look, double range)
        {
            //// what voxel are we on 
            int intX = fastfloor(Postion.X);
            int intY = fastfloor(Postion.Y);
            int intZ = fastfloor(Postion.Z);

            // which way we go
            int stepX = Math.Sign(Look.X);
            int stepY = Math.Sign(Look.Y);
            int stepZ = Math.Sign(Look.Z);

            // Calculate cell boundaries. when the step is positive, the
            // next cell is after this one meening we add 1. 
            // If negative, cell is
            // before this one in which case dont add to boundary 
            int boundaryX = intX + (stepX > 0 ? 1 : 0);
            int boundaryY = intY + (stepY > 0 ? 1 : 0);
            int boundaryZ = intZ + (stepZ > 0 ? 1 : 0);

            // how far do we have to walk before hitting a voxel boundary 
            // may be infinite
            double tMaxX = (boundaryX - Postion.X) / Look.X;
            double tMaxY = (boundaryY - Postion.Y) / Look.Y;
            double tMaxZ = (boundaryZ - Postion.Z) / Look.Z;

            // how far do we have to walk before crossing a cell 
            // may be infinite
            double tDeltaX = stepX / Look.X;
            double tDeltaY = stepY / Look.Y;
            double tDeltaZ = stepZ / Look.Z;

            // which face was intersected
            BlockFace facex = (stepX > 0 ? BlockFace.Front : BlockFace.Back);
            BlockFace facey = (stepY > 0 ? BlockFace.Bottom : BlockFace.Top);
            BlockFace facez = (stepZ > 0 ? BlockFace.Left : BlockFace.Right);
            BlockFace face = facex;

            // start walking and returning the intersecting voxels
            for (double i = 0.8D; i < range; i += 0.5D)
            {
                yield return new Hit
                {
                    x = intX,
                    y = intY,
                    z = intZ,
                    face = face
                };

                if (tMaxX < tMaxY && tMaxX < tMaxZ)
                {
                    intX += stepX;
                    tMaxX += tDeltaX;
                    face = facex;
                }
                else if (tMaxY < tMaxZ)
                {
                    intY += stepY;
                    tMaxY += tDeltaY;
                    face = facey;
                }
                else
                {
                    intZ += stepZ;
                    tMaxZ += tDeltaZ;
                    face = facez;
                }
            }
        }
    }
}